﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using NUnit.Framework;
using EnumUtils;


namespace EnumUtilsTests
{
  [TestFixture]
  public class EnumFlagsUtilitiesTests
  {
    [Test]
    #region ConvertEnumToBitArray Tests
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    ConvertEnumToBitArrayNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.ConvertEnumToBitArray(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }


    [Test]
    public void
    ConvertEnumToBitArrayOperationCorrectnessTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f1;
      BitArray flagEnumBits = FlagEnumUtilities.ConvertEnumToBitArray(flagEnum);
      int flagEnumAsAnInt = Convert.ToInt32(flagEnum);
      BitArray flagEnumAsAnIntBits = new BitArray(new int[] { flagEnumAsAnInt });

      IEnumerator flagEnumBitsIter = flagEnumBits.GetEnumerator();
      IEnumerator flagEnumAsAnIntBitsIter = flagEnumAsAnIntBits.GetEnumerator();

      while ((flagEnumBitsIter.MoveNext()) && (flagEnumAsAnIntBitsIter.MoveNext())) {
        Assert.AreEqual(flagEnumAsAnIntBitsIter.Current, flagEnumBitsIter.Current);
      }
    }


    [Test]
    public void
    ConvertEnumToBitArrayEnumAndBitArraySeparateInstancesTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f1;
      BitArray flagEnumBits_IUT = FlagEnumUtilities.ConvertEnumToBitArray(flagEnum);
      BitArray flagEnumBits_originalValue = new BitArray(flagEnumBits_IUT);
      flagEnum = TestingEnums.GoodFlagEnum.f2;

      BitArray flagEnumBits_newValue = FlagEnumUtilities.ConvertEnumToBitArray(flagEnum);

      // prove that the BitArray instance and the Enum instance can vary 
      // separately
      Assert.AreNotEqual(flagEnumBits_newValue[0], flagEnumBits_IUT[0]);
      Assert.AreNotEqual(flagEnumBits_newValue[1], flagEnumBits_IUT[1]);
      // RJM: A while loop is not used here because all the bits after the 
      //      2nd element should be the same.

      // prove that the BitArray did not, in fact, change
      IEnumerator flagEnumBits_IUTIter = flagEnumBits_IUT.GetEnumerator();
      IEnumerator flagEnumBits_originalValueIter = flagEnumBits_originalValue.GetEnumerator();
      while (flagEnumBits_IUTIter.MoveNext() && flagEnumBits_originalValueIter.MoveNext()) {
        Assert.AreEqual(flagEnumBits_originalValueIter.Current, flagEnumBits_IUTIter.Current);
      }
    } 
    #endregion



    #region DoesEnumHaveFlagsAttribute Tests
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    DoesEnumHaveFlagsAttributeNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.DoesEnumHaveFlagsAttribute(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }
    //TODO: Evaluate utility of EnumFlagUtilities.ConvertIntToEnum() method (might duplicate the BCL)


    [Test]
    public void
    DoesEnumHaveFlagsAttributeTrueWithoutFlagsAttributeTest( ) {
      Enum regularEnum = TestingEnums.RegularEnum.f3;
      Assert.IsFalse(FlagEnumUtilities.DoesEnumHaveFlagsAttribute(regularEnum));
    }


    [Test]
    public void
    DoesEnumHaveFlagsAttributeFalseWithFlagsAttributeTest( ) {
      Enum flagsEnum = TestingEnums.GoodFlagEnum.f3;
      Assert.IsTrue(FlagEnumUtilities.DoesEnumHaveFlagsAttribute(flagsEnum));
    } 
    #endregion


    #region GenerateBitmask Tests
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    GenerateBitmaskEnumNullTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.GenerateBitmask(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }


    [Test]
    [ExpectedException(typeof(ArgumentException))]
    public void
    GenerateBitmaskRegularEnumTest( ) {
      Enum regularEnum = TestingEnums.RegularEnum.f1;
      FlagEnumUtilities.GenerateBitmask(regularEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentException));
    }


    [Test]
    public void
    GenerateBitmaskOperationalCorrectnessTest( ) {
      // use bondary testing: 2^5, 2^5-1, 2^5+1
      Enum flagEnum = TestingEnums.GoodFlagEnum.f6;
      Assert.AreEqual((64 - 1), FlagEnumUtilities.GenerateBitmask(flagEnum));
    }
    #endregion



    #region ForeachEnumFlag Tests
    public event Action<bool> OperationExecuted;
    private bool m_hasOperationExecuted = false;
    private uint executionCount = 0;

    void DidOperationExecuted( bool didOperationExecuteSuccessfully ) {
      this.m_hasOperationExecuted = didOperationExecuteSuccessfully;
    }

    
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    ForeachEnumFlagNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      Action<Enum> operation = flag => Console.WriteLine(flag);
      FlagEnumUtilities.ForeachEnumFlag(nullEnum, operation);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }


    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    ForeachEnumFlagNullOperationTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f3;
      Action<Enum> nullOperation = default(Action<Enum>);
      FlagEnumUtilities.ForeachEnumFlag(flagEnum, nullOperation);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }


    [Test]
    [ExpectedException(typeof(ArgumentException))]
    public void
    ForeachEnumFlagRegularEnumTest( ) {
      Enum regularEnum = TestingEnums.RegularEnum.f1;
      Action<Enum> operation = flag => Console.WriteLine(flag);
      FlagEnumUtilities.ForeachEnumFlag(regularEnum, operation);

      Assert.Fail("{0} not thrown.", typeof(ArgumentException));
    }


    [Test]
    public void
    ForeachEnumFlagDoesOperationExecuteOnAllFlagsTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.All;
      Action<Enum> operation = flag => { Console.WriteLine(flag); ++this.executionCount; };
      FlagEnumUtilities.ForeachEnumFlag(flagEnum, operation);

      var possibleEnumValues = Enum.GetValues(flagEnum.GetType());
      dynamic maxEnumValue = Convert.ChangeType(possibleEnumValues.GetValue(possibleEnumValues.GetUpperBound(0)), Enum.GetUnderlyingType(flagEnum.GetType()));
      Assert.AreEqual((int)Math.Round(Math.Log(maxEnumValue, 2)), this.executionCount);
    }


    [Test]
    public void
    ForeachEnumFlagDoesOperationExecuteOnEnumFlagsTest( ) {
      OperationExecuted += new Action<bool>(DidOperationExecuted);

      Enum flagsEnum = TestingEnums.GoodFlagEnum_WithPreDefinedFlagCombos.f1_3_5;
      Action<Enum> operation = flag => { Console.WriteLine(flag); OperationExecuted(true); };
      FlagEnumUtilities.ForeachEnumFlag(flagsEnum, operation);

      Assert.IsTrue(this.m_hasOperationExecuted, "Foreach operation on flags enum failed.");
    }
    #endregion



    #region Enumerator-getting related tests
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    GetEnumeratorNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.GetEnumerator(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentException));
    }
    
    
    [Test]
    public void
    GetEnumeratorOperationCorrectnessTest( ) {
      var flagEnum = TestingEnums.GoodFlagEnum.f1 | TestingEnums.GoodFlagEnum.f4;
      
      IEnumerator flagEnumIter = FlagEnumUtilities.GetEnumerator(flagEnum);
      while (flagEnumIter.MoveNext()) {
        Assert.IsTrue(((dynamic)flagEnumIter.Current & flagEnum) == (dynamic)flagEnumIter.Current);
      }
    }


    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    GetGenericEnumeratorNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.GetGenericEnumerator(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentException));
    }


    [Test]
    public void
    GetGenericEnumeratorOperationCorrectnessTest( ) {
      var flagEnum = TestingEnums.GoodFlagEnum.f1 | TestingEnums.GoodFlagEnum.f4;

      IEnumerator<dynamic> flagEnumIter = FlagEnumUtilities.GetGenericEnumerator(flagEnum);
      while (flagEnumIter.MoveNext()) {
        Assert.IsTrue((flagEnumIter.Current & flagEnum) == (dynamic)flagEnumIter.Current);
      }
    }
    #endregion



    #region IsFlagDefined tests
    [Test]
    [ExpectedException(typeof(ArgumentNullException))]
    public void
    IsFlagDefinedNullEnumTest( ) {
      Enum nullEnum = default(Enum);
      FlagEnumUtilities.IsFlagDefined(nullEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentNullException));
    }


    [Test]
    [ExpectedException(typeof(ArgumentException))]
    public void
    IsFlagDefinedRegularEnumTest( ) {
      Enum regularEnum = TestingEnums.RegularEnum.f4;
      FlagEnumUtilities.IsFlagDefined(regularEnum);

      Assert.Fail("{0} not thrown.", typeof(ArgumentException));
    }


    [Test]
    public void
    IsFlagDefinedValidFlagFailsTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f6;
      Assert.IsTrue(FlagEnumUtilities.IsFlagDefined(flagEnum));
    }


    [Test]
    public void
    IsFlagDefinedInvalidFlagPassesTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f6;
      flagEnum = (Enum)Enum.Parse(flagEnum.GetType(), (Convert.ToInt32(flagEnum) << 1).ToString());
      Assert.IsFalse(FlagEnumUtilities.IsFlagDefined(flagEnum));
    }


    [Test]
    public void
    IsFlagDefinedMultipleFlagsWithAllValidFlagFailsTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f1 | TestingEnums.GoodFlagEnum.f3;
      Assert.IsTrue(FlagEnumUtilities.IsFlagDefined(flagEnum));
    }


    [Test]
    public void
    IsFlagDefinedMultipleFlagsWithInvalidFlagPassesTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.f6;
      flagEnum = (Enum)Enum.Parse(flagEnum.GetType(), (Convert.ToInt32(flagEnum) << 1).ToString());
      flagEnum = (dynamic)flagEnum | TestingEnums.GoodFlagEnum.f2;
      Assert.IsFalse(FlagEnumUtilities.IsFlagDefined(flagEnum));
    }


    [Test]
    public void
    IsFlagDefinedNoneFlagNotCountedWhenItShouldTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.None;
      Assert.IsTrue(FlagEnumUtilities.IsFlagDefined(flagEnum, true));
    }


    [Test]
    public void
    IsFlagDefinedNoneFlagCountedWhenItShouldntBeTest( ) {
      Enum flagEnum = TestingEnums.GoodFlagEnum.None;
      Assert.IsFalse(FlagEnumUtilities.IsFlagDefined(flagEnum));
    }
    #endregion
  }
}
